//
//  raw_vector.hpp
//  utilities
//
//  Created by Sam Jaffe on 11/9/14.
//  Copyright (c) 2014 Sam Jaffe. All rights reserved.
//

#pragma once

namespace math { namespace vector {
    template<size_t D, typename T>
    class raw_vector;
    
    typedef raw_vector<2, int> Vec2i;
    typedef raw_vector<3, int> Vec3i;
    typedef raw_vector<2, float> Vec2;
    typedef raw_vector<3, float> Vec3;
    typedef raw_vector<4, float> Vec4;
} }

namespace math { namespace vector {
    namespace detail {
        template<size_t D, typename T>
        class _raw_vector {
        public:
            typedef T value_type;
            typedef T& reference;
            typedef const T& const_reference;
            
            T& operator[] (size_t index);
            constexpr inline const T& operator[] (size_t index) const noexcept;
            
            T& at(size_t index);
            const T& at(size_t index) const;
            
            raw_vector<D, T>& operator+=(const raw_vector<D, T>& other);
            raw_vector<D, T>& operator-=(const raw_vector<D, T>& other);
            raw_vector<D, T>& operator*=(const raw_vector<D, T>& other);
            raw_vector<D, T>& operator/=(const raw_vector<D, T>& other);
            raw_vector<D, T>& operator+=(const T& c);
            raw_vector<D, T>& operator-=(const T& c);
            raw_vector<D, T>& operator*=(const T& c);
            raw_vector<D, T>& operator/=(const T& c);
            
            raw_vector<D, T> operator+(const raw_vector<D, T>& other) const;
            raw_vector<D, T> operator-(const raw_vector<D, T>& other) const;
            raw_vector<D, T> operator*(const raw_vector<D, T>& other) const;
            raw_vector<D, T> operator/(const raw_vector<D, T>& other) const;
            raw_vector<D, T> operator+(const T& c) const;
            raw_vector<D, T> operator-(const T& c) const;
            raw_vector<D, T> operator*(const T& c) const;
            raw_vector<D, T> operator/(const T& c) const;
            
            raw_vector<D, T> operator-() const;
            
//            template <size_t D2, typename = typename std::enable_if<D2 != D>::type>
//            raw_vector<D2, T> resize() const;
            
            T dot(const _raw_vector& v) const;
            T lengthSquared() const;
            auto length() const -> decltype(std::sqrt(T()));
            T distanceSquared(const raw_vector<D, T>& other) const;
            auto distance(const raw_vector<D, T>& other) const -> decltype(std::sqrt(T()));
            raw_vector<D, T> unit() const;
            
        protected:
            constexpr _raw_vector() = default;
            _raw_vector(const _raw_vector& that) = default;
            _raw_vector(_raw_vector&& that) = default;
            _raw_vector& operator=(const _raw_vector& other) = default;
            _raw_vector& operator=(_raw_vector&& other) = default;
            _raw_vector(std::initializer_list<T> vals);
            template <std::size_t D2, typename = typename std::enable_if<D2 != D>::type>
            _raw_vector(const _raw_vector<D2, T>& that);
            
        private:
            value_type data[D];
        };
    }
    
    template<size_t D, typename T>
    class raw_vector : public detail::_raw_vector<D, T> {
    public:
        typedef T value_type;
        typedef T& reference;
        typedef const T& const_reference;

        constexpr raw_vector() = default;
        raw_vector(const raw_vector& that) = default;
        raw_vector(raw_vector&& that) = default;
        raw_vector& operator=(const raw_vector& other) = default;
        raw_vector& operator=(raw_vector&& other) = default;
        raw_vector(std::initializer_list<T> vals);
        template <std::size_t D2, typename = typename std::enable_if<D2 != D>::type>
        raw_vector(const raw_vector<D2, T>& that);
        
    protected:
    private:
        template <size_t D2, typename T2> friend class detail::_raw_vector;
    };
    
    template<class T>
    class raw_vector<4, T> : public detail::_raw_vector<4, T> {
    public:
        typedef T value_type;
        typedef T& reference;
        typedef const T& const_reference;
        
        constexpr raw_vector<4, T>() = default;
        raw_vector<4, T>(const raw_vector<4, T>& that) = default;
        raw_vector<4, T>(raw_vector<4, T>&& that) = default;
        raw_vector<4, T>& operator=(const raw_vector<4, T>& other) = default;
        raw_vector<4, T>& operator=(raw_vector<4, T>&& other) = default;
        raw_vector<4, T>(std::initializer_list<T> vals);
        raw_vector<4, T>(const T& x, const T& y, const T& z, const T& w);
        template <std::size_t D2, typename = typename std::enable_if<D2 != 4>::type>
        raw_vector(const raw_vector<D2, T>& that);
        
        reference x() { return (*this)[0]; }
        const_reference x() const { return (*this)[0]; }
        reference y() { return (*this)[1]; }
        const_reference y() const { return (*this)[1]; }
        reference z() { return (*this)[2]; }
        const_reference z() const { return (*this)[2]; }
        reference w() { return (*this)[3]; }
        const_reference w() const { return (*this)[3]; }
        reference r() { return (*this)[0]; }
        const_reference r() const { return (*this)[0]; }
        reference g() { return (*this)[1]; }
        const_reference g() const { return (*this)[1]; }
        reference b() { return (*this)[2]; }
        const_reference b() const { return (*this)[2]; }
        reference a() { return (*this)[3]; }
        const_reference a() const { return (*this)[3]; }
        
    private:
        template <size_t D2, typename T2> friend class detail::_raw_vector;
    };
    
    template<class T>
    class raw_vector<3, T> : public detail::_raw_vector<3, T> {
    public:
        typedef T value_type;
        typedef T& reference;
        typedef const T& const_reference;

        constexpr raw_vector<3, T>() = default;
        raw_vector<3, T>(const raw_vector<3, T>& that) = default;
        raw_vector<3, T>(raw_vector<3, T>&& that) = default;
        raw_vector<3, T>& operator=(const raw_vector<3, T>& other) = default;
        raw_vector<3, T>& operator=(raw_vector<3, T>&& other) = default;
        raw_vector<3, T>(std::initializer_list<T> vals);
        raw_vector<3, T>(const T& x, const T& y, const T& z);
        template <std::size_t D2, typename = typename std::enable_if<D2 != 4>::type>
        raw_vector(const raw_vector<D2, T>& that);
        
        raw_vector<3, T> cross(const raw_vector<3, T>& v) const;
        
        reference x() { return (*this)[0]; }
        const_reference x() const { return (*this)[0]; }
        reference y() { return (*this)[1]; }
        const_reference y() const { return (*this)[1]; }
        reference z() { return (*this)[2]; }
        const_reference z() const { return (*this)[2]; }
        reference r() { return (*this)[0]; }
        const_reference r() const { return (*this)[0]; }
        reference g() { return (*this)[1]; }
        const_reference g() const { return (*this)[1]; }
        reference b() { return (*this)[2]; }
        const_reference b() const { return (*this)[2]; }

    private:
        template <size_t D2, typename T2> friend class detail::_raw_vector;
    };
    
    template<class T>
    class raw_vector<2, T> : public detail::_raw_vector<2, T> {
    public:
        typedef T value_type;
        typedef T& reference;
        typedef const T& const_reference;

        constexpr raw_vector<2, T>() = default;
        raw_vector<2, T>(const raw_vector<2, T>& that) = default;
        raw_vector<2, T>(raw_vector<2, T>&& that) = default;
        raw_vector<2, T>& operator=(const raw_vector<2, T>& other) = default;
        raw_vector<2, T>& operator=(raw_vector<2, T>&& other) = default;
        raw_vector<2, T>(std::initializer_list<T> vals);
        raw_vector<2, T>(const T& x, const T& y);
        template <std::size_t D2, typename = typename std::enable_if<D2 != 2>::type>
        raw_vector(const raw_vector<D2, T>& that);
        
        raw_vector<3, T> cross(const raw_vector<2, T>& v) const;
        
        reference x() { return (*this)[0]; }
        const_reference x() const { return (*this)[0]; }
        reference y() { return (*this)[1]; }
        const_reference y() const { return (*this)[1]; }
    private:
        template <size_t D2, typename T2> friend class detail::_raw_vector;
    };
} }

#include "raw_vector_impl.hpp"
